﻿using H.Daos;
using H.Daos.Attributes;
using H.Utils;
using System;
using System.Data.SQLite;
using System.Reflection;

namespace H.Dao.SQLite.Daos.Cache
{
    /// <summary>
    /// 获取值委托
    /// </summary>
    /// <param name="model"></param>
    /// <returns></returns>
    public delegate object GetMethod(AbstractEntity model);

    /// <summary>
    /// 设置值委托
    /// </summary>
    /// <param name="model"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public delegate void SetMethod(AbstractEntity model, object value);

    /// <summary>
    /// 委托从SQLiteDataReader中获取值赋值给_this模型
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="_this"></param>
    /// <param name="reader"></param>
    public delegate void ReadDbMethods(AbstractEntity model, SQLiteDataReader reader);

    /// <summary>
    /// 数据库表列缓存
    /// </summary>
    internal class FieldMapping
    {
        /// <summary>
        /// 设置值委托
        /// </summary>
        public readonly SetMethod SetMethod;

        /// <summary>
        /// 读取值委托
        /// </summary>
        public readonly GetMethod GetMethod;

        /// <summary>
        /// 从数据库读取值并映射到模型中的委托
        /// </summary>
        public readonly ReadDbMethods ReadDbMethods;

        /// <summary>
        /// 数据库中的列名
        /// </summary>
        public readonly string ColumnName;

        /// <summary>
        /// 该字段是否为主键字段
        /// </summary>
        public readonly bool IsPrimaryKey;

        /// <summary>
        /// 该字段是否允许为空
        /// </summary>
        public readonly bool IsNotNull;

        /// <summary>
        /// 是否是自动递增列
        /// </summary>
        public readonly bool IsAutoIncrement;

        /// <summary>
        /// 是否是唯一键列
        /// </summary>
        public readonly bool IsUnique;

        /// <summary>
        /// 该字段的类型
        /// </summary>
        public readonly DbType Type;

        /// <summary>
        /// 该列的默认值
        /// </summary>
        public readonly string DefaultValue;

        /// <summary>
        /// 字段映射
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="type"></param>
        /// <param name="isPrimaryKey"></param>
        /// <param name="isNotNull"></param>
        /// <param name="isAutoIncrement"></param>
        /// <param name="isUnique"></param>
        /// <param name="defaultValue"></param>
        /// <param name="readDbMethods"></param>
        /// <param name="getMethod"></param>
        /// <param name="setMethod"></param>
        private FieldMapping(string columnName, DbType type, bool isPrimaryKey, bool isNotNull,
            bool isAutoIncrement, bool isUnique, string defaultValue, ReadDbMethods readDbMethods,
            GetMethod getMethod, SetMethod setMethod)
        {
            ColumnName = columnName;
            Type = type;
            IsPrimaryKey = isPrimaryKey;
            IsNotNull = isNotNull;
            IsAutoIncrement = isAutoIncrement;
            IsUnique = isUnique;
            DefaultValue = defaultValue;

            ReadDbMethods = readDbMethods;
            GetMethod = getMethod;
            SetMethod = setMethod;
        }


        /// <summary>
        /// 构建一个值映射
        /// </summary>
        /// <param name="property"></param>
        internal static FieldMapping GetInstance(PropertyInfo property)
        {

            object[] emptyParam = new object[0];
            object[] attrs = property.GetCustomAttributes(typeof(ColumnAttribute), true);

            if (attrs.Length == 0)
            {
                return null;
            }

            string columnName;
            DbType type = DbType.NULL;
            bool isPrimaryKey = false;
            bool isNotNull = false;
            bool isAutoIncrement = false;
            bool isUnique = false;
            string defaultValue = "";
            ReadDbMethods readDbMethods;

            if (attrs.Length > 0 && attrs[0] is ColumnAttribute column)
            {
                columnName = column.Name;
                isPrimaryKey = column.PrimaryKey;
                isNotNull = column.NotNull;
                defaultValue = column.DefaultValue;
                isAutoIncrement = column.AutoIncrement;
                isUnique = column.Unique;
                type = column.Type;
            }
            else
            {
                columnName = property.Name.ToUnderScoreCase();
            }

            void setMethod(AbstractEntity model, object value)
            {
                property.GetSetMethod().Invoke(model, new object[] { value });
            }

            object getMethod(AbstractEntity model)
            {
                return property.GetGetMethod().Invoke(model, emptyParam);
            }

            string typeName = property.PropertyType.Name;
            bool nullable = false;
            if (typeName == "Nullable`1")
            {
                typeName = property.PropertyType.GetGenericArguments()[0].Name;
                nullable = true;
            }
            switch (typeName)
            {
                case "Int16":
                    readDbMethods = (model, reader) =>
                    {
                        int ordinal = reader.GetOrdinal(columnName);
                        if (reader.IsDBNull(ordinal))
                        {
                            if (nullable)
                            {
                                setMethod(model, null);
                            }
                            else
                            {
                                setMethod(model, 0);
                            }
                        }
                        else
                        {
                            setMethod(model, reader.GetInt16(reader.GetOrdinal(columnName)));
                        }
                    };
                    if (type == DbType.NULL)
                    {
                        type = DbType.INTEGER;
                    }

                    break;
                case "Int32":
                    readDbMethods = (model, reader) =>
                    {
                        int ordinal = reader.GetOrdinal(columnName);
                        if (reader.IsDBNull(ordinal))
                        {
                            if (nullable)
                            {
                                setMethod(model, null);
                            }
                            else
                            {
                                setMethod(model, 0);
                            }
                        }
                        else
                        {
                            setMethod(model, reader.GetInt32(reader.GetOrdinal(columnName)));
                        }
                    };
                    if (type == DbType.NULL)
                    {
                        type = DbType.INTEGER;
                    }

                    break;
                case "Int64":
                    readDbMethods = (model, reader) =>
                    {
                        int ordinal = reader.GetOrdinal(columnName);
                        if (reader.IsDBNull(ordinal))
                        {
                            if (nullable)
                            {
                                setMethod(model, null);
                            }
                            else
                            {
                                setMethod(model, 0);
                            }
                        }
                        else
                        {
                            setMethod(model, reader.GetInt64(reader.GetOrdinal(columnName)));
                        }
                    };
                    if (type == DbType.NULL)
                    {
                        type = DbType.INTEGER;
                    }

                    break;
                case "String":
                    readDbMethods = (model, reader) =>
                    {
                        int ordinal = reader.GetOrdinal(columnName);
                        if (!reader.IsDBNull(ordinal))
                        {
                            setMethod(model, reader.GetString(ordinal));
                        }
                        else
                        {
                            setMethod(model, null);
                        }
                    };
                    if (type == DbType.NULL)
                    {
                        type = DbType.TEXT;
                    }

                    break;
                case "Double":
                    readDbMethods = (model, reader) =>
                    {
                        int ordinal = reader.GetOrdinal(columnName);
                        if (reader.IsDBNull(ordinal))
                        {
                            if (nullable)
                            {
                                setMethod(model, null);
                            }
                            else
                            {
                                setMethod(model, 0);
                            }
                        }
                        else
                        {
                            setMethod(model, reader.GetDouble(reader.GetOrdinal(columnName)));
                        }
                    };
                    if (type == DbType.NULL)
                    {
                        type = DbType.REAL;
                    }
                    break;
                case "Byte[]":
                    readDbMethods = (model, reader) =>
                    {
                        int ordinal = reader.GetOrdinal(columnName);
                        if (!reader.IsDBNull(ordinal))
                        {
                            setMethod(model, reader[columnName] as byte[]);
                        }
                        else
                        {
                            setMethod(model, null);
                        }

                        setMethod(model, reader[columnName] as byte[]);
                    };
                    if (type == DbType.NULL)
                    {
                        type = DbType.BLOB;
                    }

                    break;
                default:
                    throw new Exception("无法映射类型:" + property.Name);
            }
            return new FieldMapping(columnName, type, isPrimaryKey, isNotNull, isAutoIncrement, isUnique, defaultValue, readDbMethods, getMethod, setMethod);
        }
    }
}
